Moleculer 內建的 Metrics 可以收集系統內部大量的流程指標,而且可以很簡單的定義你自己的客製化 Metrics。系統內建了幾個 Metrics 報表產生器,如: Console 、 Prometheus 、 Datadog 等。
注意, Moleculer 作者 icebob 在此討論串提到[2] ,近期的趨勢 OpenTelemetry[3] 即將成為業界標準,而它與目前內建的 Metrics 與 Tracing 模組功能幾乎相同,作者可能會在下一個版本 v0.15 切換到 OpenTelemetry ,因此你可能需要考慮是否要使用目前內建的模組。
更新,在同一個討論串作者說明 OpenTelemetry 不會在 v0.15 實施,但是會以 middleware 方式支援,範例在 next branch [7] 。
以下介紹的各種報表產生器,其中細部設定筆者未測試或確認過的部分,本文將暫時保留官方提供的範例註解說明。
假如你想使用舊版的 Metrics (小於 v0.14) ,請使用
EventLegacy的追蹤輸出器[4] 。
範例:啟用 Metrics 功能,並設定報表產生器
| 名稱 | 類型 | 預設值 | 說明 | 
|---|---|---|---|
enabled | 
<Boolean> | false | 
啟用 Metrics 功能. | 
reporter | 
<Object> | <Object[]> | null | 
Metric 報表產生器設置,詳情文章後面會說明。 | 
collectProcessMetrics | 
<Boolean> | process.env.NODE_ENV !== "test" | 
收集流程與系統相關 metrics 。 | 
collectInterval | 
<Number> | 5 | 
收集時間區段(秒) | 
defaultBuckets | 
<Number[]> | [0.005, 0.01, 0.025, 0.05, 0.1, 0.25, 0.5, 1, 2.5, 5, 10] | 
直方圖的桶值 | 
defaultQuantiles | 
<Number[]> | [0.5, 0.9, 0.95, 0.99, 0.999] | 
直方圖分位數 | 
defaultMaxAgeSeconds | 
<Number> | 60 | 
分位數的最大壽命(秒) | 
defaultAgeBuckets | 
<Number> | 10 | 
分位數計算的桶數 | 
defaultAggregator | 
<String> | sum | 
數值聚合方法 | 
moleculer.config.js
module.exports = {
    metrics: {
        enabled: true,
        reporter: [
            "Console"
        ]
    }
};
| 名稱 | 類型 | 預設值 | 說明 | 
|---|---|---|---|
includes | 
<String> | <String[]> | null | 
要輸出的 Metrics 清單 | 
excludes | 
<String> | <String[]> | null | 
不輸出的 Metrics 清單 | 
metricNamePrefix | 
<String> | null | 
Metrics 名稱前綴 | 
metricNameSuffix | 
<String> | null | 
Metrics 名稱後綴 | 
metricNameFormatter | 
<Function> | null | 
Metric 格式化函數 | 
labelNameFormatter | 
<Function> | null | 
Metric 標籤名稱格式化函數 | 
範例:選項設定方式
moleculer.config.js
module.exports = {
    metrics: {
        enabled: true,
        reporter: [
            {
                type: "Console",
                options: {
                    includes: ["moleculer.**.total"],
                    excludes: ["moleculer.broker.**", "moleculer.request.**"],
                    // 原始 "moleculer.node.type" ,加上前綴: "mol:moleculer.node.type"
                    metricNamePrefix: "mol:",
                    // 原始 "moleculer.node.type" ,加上後綴: "moleculer.node.type.value"
                    metricNameSuffix: ".value",
                    metricNameFormatter: name => name.toUpperCase().replace(/[.:]/g, "_"),
                    labelNameFormatter: name => name.toUpperCase().replace(/[.:]/g, "_")
                }
            }
        ]
    }
};
這是一個除錯用的報表產生器,它會定期將 metrics 輸出至主控台。
moleculer.config.js
module.exports = {
    metrics: {
        enabled: true,
        reporter: [
            {
                type: "Console",
                options: {
                    // 輸出頻率(秒)
                    interval: 5,
                    // 客製化 logger
                    logger: null,
                    // 使用顏色
                    colors: true,
                    // 不要輸出完整清單,只輸出變更的 metrics
                    onlyChanges: true
                }
            }
        ]
    }
};
報表產生器會將變更的內容儲存至 Comma-Separated Values (CSV) 檔案。
moleculer.config.js
module.exports = {
    metrics: {
        enabled: true,
        reporter: [
            {
                type: "CSV",
                options: {
                    // CSV 檔案輸出目錄
                    folder: "./reports/metrics",
                    // CSV 欄位分隔符號
                    delimiter: ",",
                    // CSV 段落符號
                    rowDelimiter: "\n",
                    // 儲存模式
                    // [metric] 儲存至個別的檔案
                    // [label] 按標籤儲存至個別的檔案
                    mode: "metric",
                    // 儲存的 metrics 類型
                    types: null,
                    // 儲存間隔時間(秒)
                    interval: 5,
                    // 客製化檔名格式化器
                    filenameFormatter: null,
                    // 客製化 CSV 行格式化器
                    rowFormatter: null,
                }
            }
        ]
    }
};
事件報表產生器會將 metric 值發送至 Moleculer 事件。
moleculer.config.js
module.exports = {
    metrics: {
        enabled: true,
        reporter: [
            {
                type: "Event",
                options: {
                    // 事件名稱
                    eventName: "$metrics.snapshot",
                    // 是否為廣播事件
                    broadcast: false,
                    // 事件群組
                    groups: null,
                    // 只輸出變更的 metrics
                    onlyChanges: false,
                    // 輸出頻率(秒)
                    interval: 5,
                }
            }
        ]
    }
};
Datadog 報表產生器會將 metric 發送至 Datadog 伺服器。
moleculer.config.js
module.exports = {
    metrics: {
        enabled: true,
        reporter: [
            {
                type: "Datadog",
                options: {
                    // Hostname
                    host: "my-host",
                    // Base URL
                    baseUrl: "https://api.datadoghq.com/api/",
                    // API 版本
                    apiVersion: "v1",
                    // 伺服器 URL 路徑
                    path: "/series",
                    // Datadog API Key
                    apiKey: process.env.DATADOG_API_KEY,
                    // 附加到所有 metrics 標籤的預設標籤
                    defaultLabels: (registry) => ({
                        namespace: registry.broker.namespace,
                        nodeID: registry.broker.nodeID
                    }),
                    // 輸出頻率(秒)
                    interval: 10
                }
            }
        ]
    }
};
Prometheus 報表產生器會將 metric 以 Prometheus 的格式暴露輸出,Prometheus 伺服器可以利用它收集資料,預設連接埠為 3030。
moleculer.config.js
module.exports = {
    metrics: {
        enabled: true,
        reporter: [
            {
                type: "Prometheus",
                options: {
                    // HTTP 連接埠
                    port: 3030,
                    // HTTP URL 路徑
                    path: "/metrics",
                    // 附加到所有 metrics 標籤的預設標籤
                    defaultLabels: registry => ({
                        namespace: registry.broker.namespace,
                        nodeID: registry.broker.nodeID
                    })
                }
            }
        ]
    }
};
StatsD 報表產生器會透過 UDP 將 metric 發送至 StatsD 伺服器。
moleculer.config.js
module.exports = {
    metrics: {
        enabled: true,
        reporter: [
            {
                type: "StatsD",
                options: {
                    // 伺服器 host
                    host: "localhost",
                    // 伺服器 port
                    port: 8125,
                    // 最大酬載大小
                    maxPayloadSize: 1300
                }
            }
        ]
    }
};
你也可以建立客製化的報表產生器,官方建議可以參考 Console Reporter[5] 的原始碼來修改,再實作 init 、 stop 、 metricChanged 方法。
範例:建立客製化報表產生器
my-metrics-reporter.js
const BaseReporter = require("moleculer").MetricReporters.Base;
class MyMetricsReporter extends BaseReporter {
    init() { /*...*/ }
    stop() { /*...*/ }
    metricChanged() { /*...*/ }
}
module.exports = MyMetricsReporter;
範例:使用客製化報表產生器
moleculer.config.js
const MyMetricsReporter = require("./my-metrics-reporter");
module.exports = {
    metrics: {
        enabled: true,
        reporter: [
            new MyMetricsReporter(),
        ]
    }
};
counter 計數器是對 Metric 單純的累加,它只能遞增或是歸零。例如你可以使用計數器來表示服務的請求數、任務完成數或錯誤數。速率為每分鐘。
計數器提供的方法:
increment(labels?: GenericObject, value?: number, timestamp?: number)
set(value: number, labels?: GenericObject, timestamp?: number)
gauge 測量是 metric 的一個可任意增減的單純數值。通常用於測量某個值,例如目前的記憶體使用量,但也可以用在會上下浮動的計數,例如併發請求時的數量。速率為每分鐘。
測量器提供的方法:
increment(labels?: GenericObject, value?: number, timestamp?: number)
decrement(labels?: GenericObject, value?: number, timestamp?: number)
set(value: number, labels?: GenericObject, timestamp?: number)
histogram 直方圖會採樣觀察結果並且在可配置的桶中做計數(通常是觀察請求的時間或響應的大小)。另外也提供觀察值的總和,並且可以在時間視窗內計算配置分位數。速率為每分鐘。
直方圖提供的方法:
observe(value: number, labels?: GenericObject, timestamp?: number)
info 提供關於處理程序的參數、主機名稱或版本號的字串資料或數字。
set(value: any | null, labels?: GenericObject, timestamp?: number)
你可以輕易的建立客製化 metrics 。
範例:建立一個計數器
posts.service.js
module.exports = {
    name: "posts",
    actions: {
        // Get posts
        get(ctx) {
            // 遞增 metric
            this.broker.metrics.increment("posts.get.total", 1);
            return this.posts;
        }
    },
    created() {
        // 註冊一個新的計數器 metric
        this.broker.metrics.register({
            type: "counter",
            name: "posts.get.total",
            description: "Number of requests of posts",
            unit: "request",
            rate: true // 設定速率為每分鐘
        });
    }
};
範例:建立帶有標籤的測量
posts.service.js
module.exports = {
    name: "posts",
    actions: {
		// 建立的 Action
        create(ctx) {
            // 更新 metrics
            this.broker.metrics.increment("posts.total", { userID: ctx.params.author }, 1);
            return posts;
        },
		// 刪除的 Action
        remove(ctx) {
            // 更新 metrics
            this.broker.metrics.decrement("posts.total", { userID: ctx.params.author }, 1);
            return posts;
        },
    },
    created() {
        // 註冊一個新的測量 metric
        this.broker.metrics.register({ 
            type: "gauge", 
            name: "posts.total", 
            labelNames: ["userID"]
            description: "Number of posts by user",
            unit: "post"
        });
    }
};
範例:建立包含桶與分位數設定的直方圖
posts.service.js
module.exports = {
    name: "posts",
    actions: {
        // 建立的 Action
        async create(ctx) {
            // 測量建立 post 的時間
            const timeEnd = this.broker.metrics.timer("posts.creation.time");
            const post = await this.adapter.create(ctx.params);
            const duration = timeEnd();
            this.logger.debug("Post created. Elapsed time: ", duration, "ms");
            return post;
        }
    },
    created() {
        // 註冊新的直方圖 metric
        this.broker.metrics.register({
            type: "histogram",
            name: "posts.creation.time",
            description: "Post creation time",
            unit: "millisecond",
            // 產生線性桶值
            linearBuckets: {
                start: 0,
                width: 100,
                count: 10
            },
            quantiles: [0.5, 0.9, 0.95, 0.99],
            maxAgeSeconds: 60,
            ageBuckets: 10
        });
    }
};
[1] Metrics, https://moleculer.services/docs/0.14/metrics.html
[2] Switching to OpenTelemetry from built-in metrics and tracing?, https://github.com/moleculerjs/moleculer/discussions/1125
[3] OpenTelemetry, https://opentelemetry.io/
[4] Tracing Event (legacy), https://moleculer.services/docs/0.14/tracing.html#Event-legacy
[5] Moleculer Console Reporter, https://github.com/moleculerjs/moleculer/blob/master/src/metrics/reporters/console.js
[6] Creating Buckets or Clusters for Numeric Column Values in Exploratory, https://blog.exploratory.io/d04901b32d35
[7] examples/opentelemetry, https://github.com/moleculerjs/moleculer/tree/next/examples/opentelemetry